package cn.remex.core.util;

import org.apache.http.*;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static cn.remex.core.util.WebDavFileUtil.HTTP_METHOD.*;

/**
 * Disc: 使用httpclient请求WebDav server进行文件的存取操作(待完善)
 * Created by xinxinran on 2016/12/17.
 */
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue"})
public final class WebDavFileUtil {

    private static final Logger logger = Logger.getLogger(WebDavFileUtil.class);

    private static final Map<String, String> config = PropertyReader.getPropertiesHashMap(WebDavFileUtil.class, "/sys-config.properties");

    public enum HTTP_METHOD {
        GET, POST, DELETE, PUT, OPTION
    }

    public static class HttpResponseBean {
        //  是否成功
        private boolean isSuc = true;

        //  http状态码
        private int statusCode = -1;

        //  返回数据
        private String result = null;

        //  异常信息
        private String errorMessage = null;

        public HttpResponseBean(boolean isSuc, int statusCode, String result) {
            this.isSuc = isSuc;
            this.statusCode = statusCode;
            this.result = result;
        }

        public HttpResponseBean(boolean isSuc, int statusCode, String result, String errorMessage) {
            this.isSuc = isSuc;
            this.statusCode = statusCode;
            this.result = result;
            this.errorMessage = errorMessage;
        }

        public boolean isSuc() {
            return isSuc;
        }

        public void setSuc(boolean suc) {
            isSuc = suc;
        }

        public int getStatusCode() {
            return statusCode;
        }

        public void setStatusCode(int statusCode) {
            this.statusCode = statusCode;
        }

        public String getResult() {
            return result;
        }

        public void setResult(String result) {
            this.result = result;
        }

        public String getErrorMessage() {
            return errorMessage;
        }

        public void setErrorMessage(String errorMessage) {
            this.errorMessage = errorMessage;
        }
    }

    static Pattern JSESSION_COOKIE_PATTERN = Pattern.compile("JSESSIONID=[a-fA-F0-9]+");
    private static String findJSessionCookie(String headerString){
        if(Judgment.notEmpty(headerString)){
            Matcher matcher =JSESSION_COOKIE_PATTERN.matcher(headerString);
            if(matcher.find()){
                return matcher.group();
            }
        }

        return null;
    }

    public static File downloadByGet(String url, String receiveFile) {
        return downloadByGet(url, null, null, receiveFile);
    }

    public static File downloadByGet(String url, Map<String, Object> params, String receiveFile) {
        return downloadByGet(url, params, null, receiveFile);
    }

    public static File downloadByGet(String url, Map<String, Object> params, Map<String, String> headers, String receiveFileString) {
        HttpRequestBase httpMethod = getHttpMethod(url, GET);
        setHttpParameters(httpMethod, GET, params);
        setHttpHeadrs(httpMethod, headers);

        CloseableHttpClient httpClient = HttpClients.createDefault();

        File receiveFile = new File(receiveFileString);

        InputStream in = null;
        try {
            HttpResponse response = httpClient.execute(httpMethod);
            FileOutputStream output = new FileOutputStream(receiveFile);

            in = response.getEntity().getContent();
            byte b[] = new byte[1024];
            int j;
            while ((j = in.read(b)) != -1) {
                output.write(b, 0, j);
            }
            output.flush();
            output.close();
        } catch (Exception e) {
            return null;
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception ignored) {
            }

            try {
                httpClient.close();
            } catch (IOException ignored) {
            }
        }

        return receiveFile;
    }

    public static File downloadByGetWithTomcatLogin(String loginUrl, Map<String, Object> loginParams,
                                                    String downUrl, String receiveFileString){
        HttpRequestBase httpMethod = getHttpMethod(loginUrl, POST);

        //MapHelper.toMap("username", "admin", "password", "fjc^&*678", "logicSysNo", "DKSP")
        setHttpParameters(httpMethod, POST, loginParams);

        setHttpHeadrs(httpMethod, null);

        CloseableHttpClient httpClient = HttpClients.createDefault();

        try {
            HttpResponse response = httpClient.execute(httpMethod);

            StatusLine sl = response.getStatusLine();
            HttpEntity entity = response.getEntity();


            Header[] cookieHeaders = response.getHeaders("Set-Cookie");
            String loginCookie = null;
            if(cookieHeaders != null) {
                for (Header header : cookieHeaders) {
                    String cookieExpr = header.getValue();
                    if((loginCookie = findJSessionCookie(cookieExpr)) != null){
                        break;
                    }
                }
            }

            Map<String, String> loginCookieHeader = null;
            if(loginCookie != null){
                loginCookieHeader = new HashMap<>();

                loginCookieHeader.put("Cookie", loginCookie);
            }
            return downloadByGet(downUrl, null, loginCookieHeader, receiveFileString);
        } catch (Exception e) {
            logger.error("Http Request Error {}", e);
            return null;
        } finally {
            try {
                httpClient.close();
            } catch (IOException ignored) {

            }
        }
    }

    /**
     * 进行Http协议请求
     *
     * @param url        请求的url
     * @param methodType 请求方式
     * @param params     请求的参数Map
     * @return 请求返回的ResponseBean
     */

    public static HttpResponseBean doRequestHttpWithParams(String url, HTTP_METHOD methodType, Map<String, Object> params) {
        return doRequestHttpWithParams(url, methodType, params, null);
    }


    public static HttpResponseBean doRequestHttpWithParams(String url, HTTP_METHOD methodType, Map<String, Object> params, Map<String, String> headers) {
        HttpRequestBase httpMethod = getHttpMethod(url, methodType);

        setHttpParameters(httpMethod, methodType, params);

        setHttpHeadrs(httpMethod, headers);

        CloseableHttpClient httpClient = HttpClients.createDefault();

        try {
            HttpResponse response = httpClient.execute(httpMethod);

            StatusLine sl = response.getStatusLine();
            HttpEntity entity = response.getEntity();

            return new HttpResponseBean(true, sl.getStatusCode(), EntityUtils.toString(entity));
        } catch (Exception e) {
            logger.error("Http Request Error {}", e);
            return new HttpResponseBean(false, -1, e.getMessage());
        } finally {
            try {
                httpClient.close();
            } catch (IOException ignored) {

            }
        }
    }

    public static HttpResponseBean doRequestHttpWithContents(String url, HTTP_METHOD methodType, String content) {
        return doRequestHttpWithContents(url, methodType, null, content);
    }

    public static HttpResponseBean doRequestHttpWithContents(String url, HTTP_METHOD methodType, Map<String, Object> params, String content) {
        return doRequestHttpWithContents(url, methodType, params, null, content);
    }

    public static HttpResponseBean doRequestHttpWithContents(String url, HTTP_METHOD methodType, Map<String, Object> params, Map<String, String> headers, String content) {
        if (methodType != POST && methodType != PUT) {
            logger.warn("Http request (no POST/PUT) not recommend with content, use no param request instead......");
            return doRequestHttpWithParams(url, methodType, params);
        }

        HttpRequestBase httpMethod = getHttpMethod(url, methodType);

        setHttpHeadrs(httpMethod, headers);

        if (params != null) {
            try {
                httpMethod.setURI(makeGetMethodURI(httpMethod.getURI().toString(), params));
            } catch (URISyntaxException ignored) {

            }
        }

        CloseableHttpClient httpClient = HttpClients.createDefault();

        try {

            if (methodType == POST) {
                ((HttpPost) httpMethod).setEntity(new StringEntity(content, Charset.forName("UTF-8")));
            } else {
                ((HttpPut) httpMethod).setEntity(new StringEntity(content, Charset.forName("UTF-8")));
            }

            HttpResponse response = httpClient.execute(httpMethod);

            StatusLine sl = response.getStatusLine();
            HttpEntity entity = response.getEntity();

            return new HttpResponseBean(true, sl.getStatusCode(), EntityUtils.toString(entity));
        } catch (Exception e) {
            return new HttpResponseBean(false, -1, e.getMessage());
        } finally {
            try {
                httpClient.close();
            } catch (IOException ignored) {

            }
        }
    }

    /**
     * 设置请求参数
     *
     * @param httpMethod 请求对象
     * @param methodType 请求类型
     * @param params     请求参数
     */
    private static void setHttpParameters(HttpRequestBase httpMethod, HTTP_METHOD methodType, Map<String, Object> params) {
        if (params == null || params.size() == 0) {
            return;
        }
        switch (methodType) {
            case POST:
                HttpPost postMethod = (HttpPost) httpMethod;

                boolean needMultipart = false;
                for (Map.Entry<String, Object> en : params.entrySet()) {
                    if (en.getValue() instanceof File) {
                        needMultipart = true;
                        break;
                    }
                }
                if (needMultipart) {
                    MultipartEntityBuilder reqEntity = MultipartEntityBuilder.create();
                    reqEntity.setBoundary("----WebKitFormBoundaryiDGnV9zdZA1eM1yL\r\n");
                    for (Map.Entry<String, Object> en : params.entrySet()) {
                        Object v = en.getValue();
                        if (v instanceof File) {
                            reqEntity.addPart(en.getKey(), new FileBody((File) v, ContentType.create("image/jpeg; charset=UTF-8"), ((File) v).getName()));//.addBinaryBody(en.getKey(), (File)v);
                        } else {
                            reqEntity.addTextBody(en.getKey(), String.valueOf(en.getValue()));
                        }
                    }
                    reqEntity.setCharset(Charset.forName("UTF-8"));

                    postMethod.setEntity(reqEntity.build());
                } else {
                    List<NameValuePair> postParam = new ArrayList<>();
                    for (Map.Entry<String, Object> en : params.entrySet()) {
                        postParam.add(new BasicNameValuePair(en.getKey(), String.valueOf(en.getValue())));
                    }
                    try {
                        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(postParam, "UTF-8");
                        postMethod.setEntity(entity);
                    } catch (UnsupportedEncodingException ignored) {

                    }
                }

                break;
            case GET:
                HttpGet getMethod = (HttpGet) httpMethod;
                try {
                    getMethod.setURI(makeGetMethodURI(httpMethod.getURI().toString(), params));
                } catch (URISyntaxException ignored) {

                }
                break;
            case DELETE:
                //HttpDelete delMethod = (HttpDelete) httpMethod;
                break;
            case PUT:
                //HttpPut putMethod = (HttpPut) httpMethod;
                break;
            default:
                break;
        }
    }

    private static URI makeGetMethodURI(String requestURI, Map<String, Object> params) throws URISyntaxException {
        StringBuilder sb = new StringBuilder(requestURI);
        sb.append(requestURI.contains("?") ? "&" : "?");
        for (Map.Entry<String, Object> en : params.entrySet()) {
            sb.append(en.getKey());
            sb.append("=");
            try {
                sb.append(URLEncoder.encode(String.valueOf(en.getValue()), "UTF-8"));
                sb.append("&");
            } catch (UnsupportedEncodingException ignored) {

            }
        }

        return new URI(sb.toString());
    }

    private static void setHttpHeadrs(HttpRequestBase httpMethod, Map<String, String> headers) {
        if (headers == null || headers.size() == 0) {
            return;
        }

        for (Map.Entry<String, String> entry : headers.entrySet()) {
            httpMethod.setHeader(entry.getKey(), entry.getValue());
        }
    }

    public static String uploadFileByPut(File file) {
        String fileName = file.getName();
        String suffix=fileName.substring(fileName.lastIndexOf(".") + 1);
        String path = makeRandomPath(suffix);

        String webDavPrefix = config.get("webDavPrefix");
        String url = webDavPrefix + path;

        HttpResponseBean bean = uploadFileByPut(path, file);
        if(bean.isSuc()){
            return url;
        }else{
            return null;
        }
    }

    public static String uploadFileByPutWithSuffix(InputStream fileStream, String fileSuffix) {
        return uploadFileByPutWithSuffix(fileStream, fileSuffix, "");
    }

    public static String uploadFileByPutWithSuffix(InputStream fileStream, String fileSuffix, String type) {
        String path = makeRandomPath(fileSuffix);

        String webDavPrefix = config.get("webDavPrefix");
        String url = config.get("picBrowsePrefix") + "/" + type + path;

        HttpResponseBean bean = uploadFileByPut("/" + type + path, fileStream);
        if(bean.isSuc()){
            return url;
        }else{
            return null;
        }
    }

    public static HttpResponseBean uploadFileByPut(String path, File file) {
        String webDavPrefix = config.get("webDavPrefix");
        return putHttpWithFile(webDavPrefix + path, file);
    }

    public static HttpResponseBean uploadFileByPut(String path, InputStream fileStream) {
        String webDavPrefix = config.get("webDavPrefix");
        return putHttpWithFile(webDavPrefix + path, fileStream);
    }

    /**
     * 进行Http协议请求
     *
     * @param url  请求的url
     * @param file 请求的参数Map
     * @return 请求返回的ResponseBean
     */
    public static HttpResponseBean putHttpWithFile(String url, File file) {
        return putHttpWithFile(url, file, null);
    }
    public static HttpResponseBean putHttpWithFile(String url, InputStream fileStream) {
        return putHttpWithFile(url, fileStream, null);
    }

    public static HttpResponseBean putHttpWithFile(String url, File file, Map<String, String> headers) {
        HttpPut httpMethod = (HttpPut) getHttpMethod(url, PUT);

        httpMethod.setEntity(new FileEntity(file));

        setHttpHeadrs(httpMethod, headers);

        CloseableHttpClient httpClient = HttpClients.createDefault();

        return processRequestWithFile(httpClient, httpMethod);
    }

    public static HttpResponseBean putHttpWithFile(String url, InputStream fileStream, Map<String, String> headers) {
        HttpPut httpMethod = (HttpPut) getHttpMethod(url, PUT);

        httpMethod.setEntity(new InputStreamEntity(fileStream));

        setHttpHeadrs(httpMethod, headers);

        CloseableHttpClient httpClient = HttpClients.createDefault();

        return processRequestWithFile(httpClient, httpMethod);
    }

    public static HttpResponseBean postHttpWithFile(String url, File file) {
        return putHttpWithFile(url, file, null);
    }

    public static HttpResponseBean postHttpWithFile(String url, File file, Map<String, String> headers) {
        HttpPost httpMethod = (HttpPost) getHttpMethod(url, POST);

        httpMethod.setEntity(new FileEntity(file));

        setHttpHeadrs(httpMethod, headers);

        CloseableHttpClient httpClient = HttpClients.createDefault();

        return processRequestWithFile(httpClient, httpMethod);
    }

    private static HttpResponseBean processRequestWithFile(CloseableHttpClient httpClient, HttpEntityEnclosingRequestBase httpMethod) {
        try {
            HttpResponse response = httpClient.execute(httpMethod);

            StatusLine sl = response.getStatusLine();
            HttpEntity entity = response.getEntity();

            return new HttpResponseBean(true, sl.getStatusCode(), EntityUtils.toString(entity));
        } catch (Exception e) {
            logger.error("Http Response Error {}", e);
            return new HttpResponseBean(false, -1, e.getMessage());
        } finally {
            try {
                httpClient.close();
            } catch (IOException ignored) {

            }
        }
    }

    /**
     * 获取请求对象
     *
     * @param url        请求地址
     * @param methodType 请求类型
     * @return 返回一个HttpRequest对象
     */
    private static HttpRequestBase getHttpMethod(String url, HTTP_METHOD methodType) {
        HttpRequestBase httpMethod = null;
        switch (methodType) {
            case POST:
                // 获取方法为POST的http请求
                httpMethod = new HttpPost(url);
                break;
            case GET:
                // 获取方法为GET的http请求
                httpMethod = new HttpGet(url);
                break;
            case DELETE:
                // 获取方法为DELETE的http请求
                httpMethod = new HttpDelete(url);
                break;
            case PUT:
                // 获取方法为Put的http请求
                httpMethod = new HttpPut(url);
                break;
            default:
                break;
        }

        return httpMethod;
    }

    public static String getStringContent(HttpResponseBean HttpResponseBean) {
        if (!HttpResponseBean.isSuc()) {
            logger.warn("HttpResponseBean is in failed status when get content....");
            return null;
        } else {
            if (HttpResponseBean.getStatusCode() > 399) {
                logger.warn("HttpResponseBean response statusCode is not 200,[status code : {}]"+HttpResponseBean.getStatusCode());
            }

            return HttpResponseBean.getResult();
        }
    }

    public static HttpRequestContainer makeRequest() {
        return new HttpRequestContainer();
    }

    @SuppressWarnings("unused")
    public static class HttpRequestContainer {
        private HTTP_METHOD method = null;
        private String url = null;
        private Map<String, Object> params = new HashMap<>();
        private Map<String, String> headers = new HashMap<>();
        private String content = null;
        private File file = null;
        private boolean isFinish = false;
        private boolean isContentQuery = false;

        private void setMethod(HTTP_METHOD method) {
            if (this.method != null) {
                logger.warn("this request is already set method..., will overwrite method!!!");

                if (method == DELETE || method == GET) {
                    logger.warn("Only PUT or POST request can contain body, delete body content...");
                    this.content = null;
                    this.file = null;
                }
            }

            this.method = method;
        }

        private void setUrl(String url) {
            if (this.url != null) {
                logger.warn("this request is already set url..., will overwrite url!!!");
            }
            this.url = url;

            this.isContentQuery = this.url.contains("?");
        }

        private void setContent(String content) {
            if (this.method == DELETE || this.method == GET) {
                logger.warn("Only PUT or POST request can contain body, ignore content...");
                return;
            }
            if (this.file != null) {
                logger.warn("this request is already contains a file, this file will not upload when setting a content...!!!");
                this.file = null;
            }
            if (this.content != null) {
                logger.warn("this request is already set StringContent..., will concat Content");
                this.content += content;
            } else {
                this.content = content;
            }
        }

        private void setFile(File file) {
            if (this.method == DELETE || this.method == GET) {
                logger.warn("Only PUT or POST request can contain body, ignore file...");
                return;
            }
            if (this.content != null) {
                logger.warn("this request is already contains a string body, this string body will be deleted when setting a file body...!!!");
                this.content = null;
            }

            if (this.file != null) {
                logger.warn("this request is already set a file body..., will overwrite file body...!!!");
            }

            this.file = file;
        }


        public HttpRequestContainer doGET(String url) {
            setMethod(GET);
            setUrl(url);
            return this;
        }

        public HttpRequestContainer doPOST(String url) {
            setMethod(POST);
            setUrl(url);
            return this;
        }

        public HttpRequestContainer doPUT(String url) {
            setMethod(PUT);
            setUrl(url);
            return this;
        }

        public HttpRequestContainer doDELETE(String url) {
            setMethod(DELETE);
            setUrl(url);
            return this;
        }

        public HttpRequestContainer addHeader(String key, String value) {
            this.headers.put(key, value);
            return this;
        }

        public HttpRequestContainer addHeader(Map<String, String> headerMap) {
            this.headers.putAll(headerMap);
            return this;
        }

        public HttpRequestContainer addQuery(String key, String value) {
            try {
                this.url = this.url + (this.isContentQuery ? "&" : "?") + key + "=" + URLEncoder.encode(value, "UTF-8");
                this.isContentQuery = true;
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            return this;
        }

        public HttpRequestContainer addParameter(String key, Object value) {
            this.params.put(key, value);
            return this;
        }

        public HttpRequestContainer addParameter(Map<String, Object> paraMap) {
            this.params.putAll(paraMap);
            return this;
        }

        public HttpRequestContainer addContent(String content) {
            setContent(content);
            return this;
        }

        public HttpRequestContainer addContent(String content, boolean isForce) {
            this.content = null;
            setContent(content);
            return this;
        }

        public HttpRequestContainer addFile(File file) {
            setFile(file);
            return this;
        }

        public HttpResponseBean responseToBean() {
            if (this.file == null && this.content == null) {
                return doRequestHttpWithParams(this.url, this.method, this.params, this.headers);
            } else if (this.file != null && this.content == null) {
                if (this.method == POST) {
                    return postHttpWithFile(this.url, this.file, this.headers);
                } else {
                    return putHttpWithFile(this.url, this.file, this.headers);
                }
            } else {
                return doRequestHttpWithContents(this.url, this.method, this.params, this.headers, this.content);
            }
        }

        public String responseToString() {
            return getStringContent(responseToBean());
        }

        public File responseToFile(String filenameWithPath) {
            if (this.method != GET) {
                throw new NotImplementedException();
            }

            return downloadByGet(this.url, this.params, filenameWithPath);
        }
    }

    private static class NumberFormat {
        final static char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
                '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
                'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
                'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
                'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
                'Z'};

        final static Map<Character, Integer> digitMap = new HashMap<>();

        static {
            for (int i = 0; i < digits.length; i++) {
                digitMap.put(digits[i], i);
            }
        }

        /**
         * 支持的最大进制数
         */
        public static final int MAX_RADIX = digits.length;

        /**
         * 支持的最小进制数
         */
        public static final int MIN_RADIX = 2;

        /**
         * 将长整型数值转换为指定的进制数（最大支持62进制，字母数字已经用尽）
         *
         * @param i 数值
         * @param radix 进制数
         * @return 转换结果
         */
        public static String toString(long i, int radix) {
            if (radix < MIN_RADIX || radix > MAX_RADIX)
                radix = 10;
            if (radix == 10)
                return Long.toString(i);

            final int size = 65;
            int charPos = 64;

            char[] buf = new char[size];
            boolean negative = (i < 0);

            if (!negative) {
                i = -i;
            }

            while (i <= -radix) {
                buf[charPos--] = digits[(int) (-(i % radix))];
                i = i / radix;
            }
            buf[charPos] = digits[(int) (-i)];

            if (negative) {
                buf[--charPos] = '-';
            }

            return new String(buf, charPos, (size - charPos));
        }

        private static String digits(long val, int digits) {
            long hi = 1L << (digits * 4);
            return toString(hi | (val & (hi - 1)), MAX_RADIX).substring(1);
        }
    }

    private static String makeRandomPath(String suffix) {
        UUID uuid = UUID.randomUUID();
        StringBuilder sb = new StringBuilder();
        sb.append(NumberFormat.digits(uuid.getMostSignificantBits() >> 32, 8));
        sb.append(NumberFormat.digits(uuid.getMostSignificantBits() >> 16, 4));
        sb.append(NumberFormat.digits(uuid.getMostSignificantBits(), 4));
        sb.append(NumberFormat.digits(uuid.getLeastSignificantBits() >> 48, 4));
        sb.append(NumberFormat.digits(uuid.getLeastSignificantBits(), 12));
        String shortUUID = sb.toString();

        sb = new StringBuilder(shortUUID.length() + 8);
        sb.append('/').append(shortUUID.substring(0, 2)).append('/')
                .append(shortUUID.substring(2, 4)).append('/').append(shortUUID.substring(4))
                .append(suffix);

        return sb.toString();
    }

    public static void main(String[] args) throws Exception {

    }


}
